/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.restcomm.media.core.spi.utils;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

/**
 * Case insensitive text block representation.
 *
 * This class provides time deterministic and fast methods for creating
 * and comparing short character strings like format names, attribute values, etc.
 *
 * @author kulikov
 */
public class Text implements CharSequence {
    //reference on the local buffer
    protected byte[] chars;

    //the start position
    protected int pos;

    //the length of the string
    protected int len;

    /** points to the line separator */
    private int linePointer;

    private int hash = -1;
    
    private byte zero_byte=(byte)'0';
    private byte nine_byte=(byte)'9';
    private byte a_byte=(byte)'a';
    private byte f_byte=(byte)'f';
    private byte A_byte=(byte)'A';
    private byte F_byte=(byte)'F';
    private byte minus_byte=(byte)'-';
    
    /**
     * Create an empty text object.
     *
     * This object later can be strained on some memory area
     */
    public Text() {
    }

    /**
     * Creates new object with specified text.
     *
     * @param another the text object.
     */
    protected Text(Text another) {
        this.chars = another.chars;
        this.pos = another.pos;
        this.len = another.len;
    }
    
    /**
     * Creates new instance with specified text.
     *
     * @param s the text value.
     */
    public Text(String s) {
        this.chars = s.getBytes();
        this.pos = 0;
        this.len = chars.length;
    }

    /**
     * Creates new text matching to specified integer number.
     * 
     * @param i the integer number.
     */
    public Text(int i) {
        chars = new byte[10];
        int remainder = i;
        
        
        while (remainder >= 10) {
            chars[len++] = (byte)(remainder % 10 + 48);
            remainder /= 10;
        }
        
        chars[len++] = (byte)(remainder + 48);
        int n = len/ 2;
        
        byte b = 0;
        
        for (int k = 0; k < n; k++) {
            b = chars[len - 1 - k];
            chars[len - 1 - k] = chars[k];
            chars[k] = b;
        }
        
        
        pos = 0;
    }
    
    /**
     * Creates new instance and straining it into memory.
     * @param data memory
     * @param pos initial position
     * @param len the length from the initial position
     */
    public Text(byte[] data, int pos, int len) {
        this.chars = data;
        this.pos = pos;
        this.len = len;
    }

    /**
     * Strains this object into the memory area.
     *
     * @param data the memory area
     * @param pos the initial position
     * @param len the length of area to use
     */
    public void strain(byte[] data, int pos, int len) {
        this.chars = data;
        this.pos = pos;
        this.len = len;
        this.linePointer = 0;
    }

    /**
     * (Non Java-doc.)
     * @see java.lang.CharSequence#length()
     */
    public int length() {
        return len;
    }

    /**
     * (Non Java-doc.)
     * @see java.lang.CharSequence#charAt(int)
     */
    public char charAt(int index) {
        return (char) chars[pos + index];
    }

    /**
     * Copies reference to another text object.
     * 
     * @param destination the another text object
     */
    public void copy(Text destination) {
        destination.strain(chars, pos, len);
    }
    
    /**
     * Copies data from this buffer to another buffer.
     * 
     * @param destination the pointer to another buffer.
     */
    public void duplicate(Text destination) {
        System.arraycopy(chars, pos, destination.chars, destination.pos, len);
        destination.len = len;
    }
    
    /**
     * Divides text into parts using given separator and writes results
     * into the parts array.
     * 
     * @param separator the character used for splitting
     * @param parts the array used to hold parts of the text
     * @return the number of parts.
     */
    public int divide(char separator, Text[] parts) {
        int pointer = pos;
        int limit = pos + len;
        int mark = pointer;
        int count = 0;
        
        while (pointer < limit) {
            if (chars[pointer] == separator) {
                parts[count].strain(chars, mark, pointer - mark);
                mark = pointer + 1;
                count++;
                
                if (count == parts.length - 1) {
                    break;
                }
            }
            pointer++;
        }
        
        if(limit > mark)
        {
        	parts[count].strain(chars, mark, limit - mark);
        	count++;
        }
        
        return count;
    }
    
    public int divide(char[] separators, Text[] parts) {
        int pointer = pos;
        int limit = pos + len;
        int mark = pointer;
        int count = 0;
        int k = 0;
        
        while (pointer < limit) {
            if (chars[pointer] == separators[k]) {
                parts[count].strain(chars, mark, pointer - mark);
                mark = pointer + 1;
                k++;
                count++;
                
//                if (count == parts.length - 1) {
//                    break;
//                }
            }
            pointer++;
        }
        
        if (limit > mark) {
            parts[count].strain(chars, mark, limit - mark);
            count++;
        }
        
        return count;
    }
    
    /**
     * (Non Java-doc.)
     * @see java.lang.CharSequence#subSequence(int, int);
     */
    public CharSequence subSequence(int start, int end) {
        return subtext(start, end);
    }
    
    public Text subtext(int start, int end) {
    	return new Text(chars, this.pos + start, end - start);
    }
    
    public Text subtext(int start) {
    	return new Text(this.chars, this.pos + start, this.len);
    }

    /**
     * Splits text into partitions.
     * 
     * @param separator character used for partitioning
     * @return array of text strings.
     */
    public Collection<Text> split(char separator) {
        int pointer = pos;
        int limit = pos + len;
        int mark = pointer;

        ArrayList<Text> tokens = new ArrayList<Text>();
        while (pointer < limit) {
            if (chars[pointer] == separator) {
                tokens.add(new Text(chars, mark, pointer - mark));
                mark = pointer + 1;
            }
            pointer++;
        }
        
        tokens.add(new Text(chars, mark, limit - mark));        
        return tokens;
    }

    /**
     * Splits text into partitions.
     * 
     * @param separator character used for partitioning
     * @return array of text strings.
     */
    public Collection<Text> split(Text separator) {
    	ArrayList<Text> tokens = new ArrayList<Text>();
    	int pointer = pos;
        int limit = pos + len;
        int mark = pointer;
        
        if(separator.length()==0)
    	{
        	tokens.add(new Text(chars, mark, pointer - mark));
    		return tokens;
    	}
    	
        int index=0;
        while (pointer < limit) {
            if (chars[pointer] == separator.charAt(index))
                index++;
            else 
            	index=0;
                        
            if(index==separator.length())
            {
            	tokens.add(new Text(chars, mark, pointer - mark));
                mark = pointer + 1;
                index=0;
            }
            
            pointer++;                    
        }
        
        tokens.add(new Text(chars, mark, limit - mark));        
        return tokens;
    }
    
    public int indexOf(Text value) {
    	int pointer = pos;
        int limit = pos + len;
        int mark = pointer;
        
        if(value.length()==0)
    		return 0;
    	
        int index=0;
        while (pointer < limit) {
            if (chars[pointer] == value.charAt(index))
                index++;
            else 
            {
            	index=0;
            	mark++;
            	pointer=mark;
            }
            
            if(index==value.length())
            	return mark;
            
            pointer++;                    
        }
        
        return -1;
    }
    
    public int indexOf(char value) {
        int limit = this.pos + this.len;
        for (int pointer = this.pos; pointer < limit; pointer++) {
			if(value == this.chars[pointer]) {
				return pointer;
			}
		}
        return -1;
    }
    
    /**
     * Removes whitespace from the head and tail of the string.
     * 
     */
    public void trim() {
    	try
    	{
    	//cut white spaces from the head
		while (len>0 && chars[pos] == ' ') {
			pos++;
			len--;
		}
    	
        //cut from the end
        while (len>0 && (chars[pos + len - 1] == ' ' || chars[pos + len - 1] == '\n' || chars[pos + len - 1] == '\r')) {
            len--;
        }
    	}
    	catch(Exception e)
    	{
    		System.out.println("len:" + len + ",pos:" + pos);
    	}
    }

    /**
     * Extracts next line from this text.
     * 
     * @return
     */
    public Text nextLine() {
        if (linePointer == 0) {
            linePointer = pos;
        } else {
            linePointer++;
        }

        int mark = linePointer;
        int limit = pos + len;

        while (linePointer < limit && chars[linePointer] != '\n') {
            linePointer++;
        }
        
        return new Text(chars, mark, linePointer - mark);
    }

    /**
     * Shows is this text contains more lines.
     *
     * @return true if there are more lines 
     */
    public boolean hasMoreLines() {
        return linePointer < pos + len;
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }

        if (!(other instanceof Text)) {
            return false;
        }

        Text t = (Text) other;
        if (this.len != t.len) {
            return false;
        }

        //return this.hashCode() == t.hashCode();
        return compareChars(t.chars, t.pos);
    }
    
    @Override
    public int hashCode() {
        if (hash == -1) {
            hash = 67 * 7 + Arrays.hashCode(this.chars);
        }
        return hash;
    }

    /**
     * Compares specified character string with current string.
     * The upper and lower case characters are considered as equals.
     *
     * @param chars the string to be compared
     * @return true if specified string equals to current
     */
    private boolean compareChars(byte[] chars, int pos) {
        for (int i = 0; i < len; i++) {
            if (differentChars((char) this.chars[i + this.pos], (char) chars[i + pos])) return false;
        }
        return true;
    }
    
	/**
	 * Indicates whether the text starts with a certain pattern.
	 * 
	 * @param pattern
	 *            The pattern to match
	 * @return <code>true</code> if this text starts with the pattern.
	 *         Otherwise, or if the pattern is null, returns <code>false</code>.
	 * @author hrosa
	 */
    public boolean startsWith(Text pattern) {
    	if (pattern == null) {
    		return false;
    	}
    	return this.subSequence(0, pattern.len).equals(pattern);
    }

    /**
     * Compares two chars.
     *
     * @param c1 the first char
     * @param c2 the second char
     * @return true if first and second chars are different.
     */
    private boolean differentChars(char c1, char c2) {
        if (65 <= c1 && c1 < 97) {
            c1 +=32;
        }

        if (65 <= c2 && c2 < 97) {
            c2 +=32;
        }
        return c1 != c2;
    }

    @Override
    public String toString() {
        return new String(chars, pos, len).trim();
    }

    /**
     * Converts string value to integer
     * @return integer value
     */
    public int toInteger() throws NumberFormatException {
        int res = 0;
        byte currChar;
        int i=1;
        currChar=chars[pos];
        boolean isMinus=false;
        
        if(currChar==minus_byte)
        	isMinus=true;
        else if(currChar>=zero_byte && currChar<=nine_byte)
    		res+=currChar-zero_byte;
    	else
    		throw new NumberFormatException("value is not numeric");
    	
        for (; i < len; i++) {
        	currChar=chars[pos+i];        		
        	if(currChar>=zero_byte && currChar<=nine_byte)
        	{
        		res*=10;
        		res+=currChar-zero_byte;
        	}        
        	else
        		throw new NumberFormatException("value is not numeric");
        }
        
        if(isMinus)
        	return 0-res;
        else
        	return res;
    }
    
    /**
     * Converts string value to long
     * @return long value
     */
    public long toLong() throws NumberFormatException {
        long res = 0;
        byte currChar;
        int i=1;
        currChar=chars[pos];
        boolean isMinus=false;
        
        if(currChar==minus_byte)
        	isMinus=true;
        else if(currChar>=zero_byte && currChar<=nine_byte)
    		res+=currChar-zero_byte;
    	else
    		throw new NumberFormatException("value is not numeric");
    	
        for (; i < len; i++) {
        	currChar=chars[pos+i];        		
        	if(currChar>=zero_byte && currChar<=nine_byte)
        	{
        		res*=10;
        		res+=currChar-zero_byte;
        	}        
        	else
        		throw new NumberFormatException("value is not numeric");
        }
        
        if(isMinus)
        	return 0-res;
        else
        	return res;
    }

    public int hexToInteger() throws NumberFormatException {
    	int res = 0;
    	byte currChar;
        for (int i = 0; i < len; i++) {
        	res<<=4;
        	currChar=chars[pos+i];
        	if(currChar>=zero_byte && currChar<=nine_byte)
        		res|=currChar-zero_byte;
        	else if(currChar>=a_byte && currChar<=f_byte)
        		res|=currChar-a_byte+10;
        	else if(currChar>=A_byte && currChar<=F_byte)
        		res|=currChar-a_byte+10;
        	else 
        	{
        		System.out.println("THROWING 1");
        		throw new NumberFormatException("value is not numeric");
        	}
        }
        
        return res;
    }
    
    /**
     * 10^a
     * 
     * @param a the power
     * @return the value of 10^a
     */
    private int pow(int a) {
        int res = 1;
        for (int i = 0; i < a; i++) {
            res *= 10;
        }
        return res;
    }

    /**
     * Converts character to digit.
     *
     * @param pos the position of the character
     * @return the digit value
     */
    private int digit(int pos) {
        return chars[pos] - 48;
    }        
    
    /**
     * Writes this text into byte buffer.
     * 
     * @param buffer the buffer for writing.
     */
    public void write(ByteBuffer buffer) {
        buffer.put(chars, pos, len);
    }
    
    /**
     * Copies substring from the current line upto the end to the specified destination.
     * 
     * @param other the destination object
     */
    public void copyRemainder(Text other) {
        other.chars = this.chars;
        other.pos = this.linePointer + 1;
        other.len = this.len - this.linePointer - 1;
    }
    
    /**
     * Checks does specified symbol is present in this text.
     * 
     * @param c the character to verify.
     * @return true if character c is presented in this text and false otherwise.
     */
    public boolean contains(char c) {
        for (int k = pos; k < len; k++) {
            if (chars[k] == c) return true;
        }
        return false;
    }
}
